package me.ydq.misc;

import lombok.SneakyThrows;

import java.beans.Introspector;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

public interface Fn<T, R> extends Function<T, R>, Serializable {

    Map<Fn<?, ?>, String> UNDERLINE_CACHE = new ConcurrentHashMap<>();
    Map<Fn<?, ?>, Method> METHOD_CACHE    = new ConcurrentHashMap<>();

    default String toUnderLine() {
        return UNDERLINE_CACHE.computeIfAbsent(this, fn -> camelToUnderline($name()));
    }

    default String toFieldName() {
        return Introspector.decapitalize($name());
    }

    default Method toMethod(Class<T> cls) {
        return METHOD_CACHE.computeIfAbsent(this, fn -> Fn.fnMethod(cls, fn));
    }

    default Class<R> returnType(Class<T> cls) {
        //noinspection unchecked
        return (Class<R>) toMethod(cls).getReturnType();
    }

    /**
     * getter 去掉 get/is 之后的名称
     */
    private String $name() {
        var lambda = serialize();
        var name   = lambda.getImplMethodName();
        if (name.startsWith("get")) {
            name = name.substring(3);
        } else if (name.startsWith("is")) {
            name = name.substring(2);
        }
        return name;
    }

    /**
     * 内部不会单独调用，上游（如转下划线风格名称、获取返回值类型、获取方法）会做缓存，因此该方法不单独做缓存
     */
    @SneakyThrows
    default SerializedLambda serialize() {
        Method method = this.getClass().getDeclaredMethod("writeReplace");
        method.setAccessible(true);
        return (SerializedLambda) method.invoke(this);
    }


    /**
     * 外部调用 自行做好上游缓存
     */
    @SneakyThrows
    static Method fnMethod(Class<?> cls, Fn<?, ?> fn) {
        var lambda = fn.serialize();
        var getter = lambda.getImplMethodName();
        //默认假定所有的 getter 是 public 的，所以这里直接getMethod，这样可以获取到父类上面的 getter
        var method = cls.getMethod(getter);
        method.setAccessible(true);
        return method;
    }

    static String camelToUnderline(String camelCase) {
        if (camelCase == null || camelCase.isEmpty()) {
            return camelCase;
        }

        StringBuilder result = new StringBuilder();
        for (int i = 0; i < camelCase.length(); i++) {
            char currentChar = camelCase.charAt(i);

            if (Character.isUpperCase(currentChar)) {
                // 在大写字母前添加下划线（除了第一个字符）
                if (i > 0) {
                    result.append('_');
                }
                result.append(Character.toLowerCase(currentChar));
            } else {
                result.append(currentChar);
            }
        }

        return result.toString();
    }
}
